//
// Created by root on 6/18/17.
//

#include <stdlib.h>
#include "runner_fa.h"

static void test_alloc_transit_node() {
    TransitNode *node = alloc_transit_node();
    assert_non_null(node);
}

static State * goto_next(State *state) {
    HashTable stateTable = getStateTable();
    HashNode *node = (HashNode *) get_table_element(stateTable, state, hash_code_state);
    return ((State *) (((TransitNode *)(node->element))->next));
}

static State * exit_loop(State *state) {
    HashTable stateTable = getStateTable();
    HashNode *node = (HashNode *) get_table_element(stateTable, state, hash_code_state);
    return ((State *) (((TransitNode *)(node->next->element))->next));
}

static void test_to_dfa() {
    assert_null(to_dfa(NULL));

    // {define function {add}: {add {a} {b}} as {{a} + {b}}}
    State *state1 = to_dfa(create_statement_1());
    // {case {when {a} then {b} , ...} else {c}}
    State *state2 = to_dfa(create_loop_statement_1());
    // {let {{a} = {b}, ...} in {{c} ...}}
    State *state3 = to_dfa(create_loop_statement_2());

    State *state = state1;          //[start]
    assert_false(state->accept);
    state = goto_next(state);       //define
    assert_false(state->accept);
    state = goto_next(state);       //function
    assert_false(state->accept);
    state = goto_next(state);       //{add}
    assert_false(state->accept);
    state = goto_next(state);       //:
    assert_false(state->accept);
    state = goto_next(state);       //{add {a} {b}}
    assert_false(state->accept);
    state = goto_next(state);       //as
    assert_false(state->accept);
    state = goto_next(state);       //{{a} + {b}}
    assert_true(state->accept);

    state = state2;                 //[start]
    assert_false(state->accept);
    state = goto_next(state);       //case
    assert_false(state->accept);

    state = goto_next(state);       //when
    assert_false(state->accept);
    state = goto_next(state);       //{a}
    assert_false(state->accept);
    state = goto_next(state);       //then
    assert_false(state->accept);
    state = goto_next(state);       //{b}
    assert_false(state->accept);
    //state = goto_next(state);       //,
    //assert_false(state->accept);

    state = goto_next(state);       //when
    assert_false(state->accept);
    state = goto_next(state);       //{a}
    assert_false(state->accept);
    state = goto_next(state);       //then
    assert_false(state->accept);
    state = goto_next(state);       //{b}
    assert_false(state->accept);
    //state = goto_next(state);       //,
    //assert_false(state->accept);

    state = exit_loop(state);       //else -- exit_loop
    assert_false(state->accept);
    state = goto_next(state);       //{c}
    assert_true(state->accept);

    state = state3;                 //[start]
    assert_false(state->accept);
    state = goto_next(state);       //let
    assert_false(state->accept);
    state = goto_next(state);       //{a}
    assert_false(state->accept);
    state = goto_next(state);       //=
    assert_false(state->accept);
    state = goto_next(state);       //{b}
    assert_false(state->accept);
    //state = goto_next(state);       //,
    //assert_false(state->accept);
    state = goto_next(state);       //{a}
    assert_false(state->accept);
    state = goto_next(state);       //=
    assert_false(state->accept);
    state = goto_next(state);       //{b}
    assert_false(state->accept);
    //state = goto_next(state);       //,
    //assert_false(state->accept);
    state = exit_loop(state);       //in -- exit_loop
    assert_true(state->accept);
    state = goto_next(state);       //{c}
    assert_true(state->accept);
    state = goto_next(state);       //{c}
    assert_true(state->accept);
}

static void test_transit() {
    Mapping *mapping = create_mapping_2();

    // {{x} or {y}}
    State *starter = to_dfa(mapping->sourceStatement);
    Statement *s1 = create_mapping_1()->sourceStatement;
    Statement *s2 = create_mapping_1()->sourceStatement;
    State *state = starter;
    Queue *repQueue = init_queue();

    state = transit(starter, state, s1, repQueue);
    state = transit(starter, state, init_sym("or"), repQueue);
    state = transit(starter, state, s2, repQueue);
    assert_true(state->accept);
    KeyValueNode *node1 = get_queue_element(repQueue, 0);
    KeyValueNode *node2 = get_queue_element(repQueue, 1);

    assert_string_equal("x", node1->key);
    assert_string_equal("y", node2->key);
    assert_ptr_equal(node1->value, s1);
    assert_ptr_equal(node2->value, s2);
}

static void test_get_transit_node_from_table() {
    // {let {{var}={value}, ...} in {{expr} ...}}
    State *state1 = to_dfa(create_loop_statement_2());
    State *state = state1;
    TransitNode *node = NULL;
    node = get_transit_node_from_table(state1, state, init_sym("let"));
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_sym("="));
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    //state = goto_next(state);
    //node = get_transit_node_from_table(state1, state, init_sym(","));
    //assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_sym("="));
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    //state = goto_next(state);
    //node = get_transit_node_from_table(state1, state, init_sym(","));
    //assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_sym("in"));
    assert_non_null(node);
    state = exit_loop(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
    state = goto_next(state);
    node = get_transit_node_from_table(state1, state, init_arg());
    assert_non_null(node);
}

// {{{1} * {2}} + {3}}
static Statement *create_source_statement_for_match() {
    Statement *s = (Statement *)malloc(sizeof(Statement));
    Statement *arg1 = NULL;
    Statement *arg2 = NULL;
    Statement *arg3 = NULL;
    Statement *arg4 = NULL;
    s->symbol = NULL;
    s->type = ARG;
    s->children = init_queue();
    arg1 = add_statement(s, NULL, ARG);

    arg3 = add_statement(arg1, NULL, ARG);
    add_statement(arg3, "1", SYM);
    add_statement(arg1, "*", SYM);
    arg4 = add_statement(arg1, NULL, ARG);
    add_statement(arg4, "2", SYM);
    add_statement(s, "+", SYM);
    arg2 = add_statement(s, NULL, ARG);
    add_statement(arg2, "3", SYM);
    return s;
}

static void test_test_match() {
    // {{x} + {y}} -> {(+ {x} {y})}
    Mapping *mapping = create_mapping_1();
    Statement *statement = create_source_statement_for_match();
    Queue *replaceQueue = init_queue();
    State *state = test_match(mapping, statement, replaceQueue);

    assert_non_null(state);
    Statement *statement1 = find_value(replaceQueue, "x");
    Statement *statement2 = find_value(replaceQueue, "y");
    assert_string_equal(to_string_statement(statement1), "{{1} * {2}}");
    assert_string_equal(to_string_statement(statement2), "{3}");
}

int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_alloc_transit_node),
            cmocka_unit_test(test_to_dfa),
            cmocka_unit_test(test_transit),
            cmocka_unit_test(test_get_transit_node_from_table),
            cmocka_unit_test(test_test_match),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
